Variables

Datos Iniciales

Train

library(data.table)
library(tidyverse)
train <- fread("../data/train.csv", encoding = "UTF-8") %>% 
  select(-c(Id, property_type, operation_type, currency)) %>% 
  mutate(rooms = factor(rooms),
         bedrooms = factor(bedrooms),
         bathrooms = factor(bathrooms))
head(train)

Test

test <- fread("../data/test.csv", encoding = "UTF-8") %>% 
  select(-c(Id, property_type, operation_type, currency)) %>% 
  mutate(rooms = factor(rooms),
         bedrooms = factor(bedrooms),
         bathrooms = factor(bathrooms))
head(test)

Sample Submission

sampleSub <- fread("../data/sampleSub.csv", encoding = "UTF-8")
head(sampleSub)

Exploratorio Train

Tamaño muestral

library(tidyverse)
library(treemap)
train %>% 
  group_by(pais, provincia_departamento) %>% 
  count(name = "total") %>% 
  treemap(.,
        index = c("pais","provincia_departamento"),
        vSize = "total", 
        type = "index", 
        palette = c("#1C8356", "#C4451C"),
        title = "Tamaño muestral: País - Departamento",   
        fontsize.title = 12
 
)

NA
NA
train %>% 
  group_by(pais, rooms) %>% 
  count(name = "total") %>% 
  treemap(.,
        index = c("pais","rooms"),
        vSize = "total", 
        type = "index", 
        palette = c("#1C8356", "#C4451C"),
        title = "Tamaño muestral: País - # de Salas",   
        fontsize.title = 12
 
)

train %>% 
  group_by(pais, bedrooms) %>% 
  count(name = "total") %>% 
  treemap(.,
        index = c("pais","bedrooms"),
        vSize = "total", 
        type = "index", 
        palette = c("#1C8356", "#C4451C"),
        title = "Tamaño muestral: País - # de Dormitorios",   
        fontsize.title = 12
 
)

train %>% 
  group_by(pais, bathrooms) %>% 
  count(name = "total") %>% 
  treemap(.,
        index = c("pais","bathrooms"),
        vSize = "total", 
        type = "index", 
        palette = c("#1C8356", "#C4451C"),
        title = "Tamaño muestral: País - # de Baños",   
        fontsize.title = 12
 
)

Distribuciones

  • Baños, Dormitorios y Salas:
library(ggthemes)
train %>% 
  select(rooms, bedrooms, bathrooms) %>% 
  gather() %>% 
  group_by(key, value) %>% 
  count(name = "Total") %>% 
  ggplot(aes(x = value, y = Total)) +
  facet_wrap(~key, scales = "free") +
  geom_point(size = 2, color = "#C4451C") +
  geom_segment(aes(y = 0, xend = value, yend = Total), color = "#1C8356") +
  scale_x_continuous(n.breaks = 10) +
  theme_fivethirtyeight()

  • Precio y área en escala original y logarítmica:

Comparativos

  • Distribución de precios y área por número de habitaciones:
train %>% 
  select(rooms, price, surface_total) %>% 
  mutate(rooms = factor(rooms)) %>% 
  mutate(priceLog = log(price),
         surfaceLog = log(surface_total)) %>% 
  gather(key = "key", value = "valor", -c(rooms)) %>% 
  ggplot(aes(x = rooms, y = valor)) +
  facet_wrap(~key, scales = "free") +
  geom_boxplot(outlier.alpha = 0.01, fill = "#1C8356", alpha = 0.5,
               color = "#C4451C", size = 0.1) +
  stat_summary(fun.y = mean, geom = "point", color = "#C4451C", size = 2,
               shape = 17) +
  theme_fivethirtyeight() +
  labs(caption = "Triángulo = promedio", subtitle = "Habitaciones")

  • Distribución de precios y área por número de dormitorios:
train %>% 
  select(bedrooms, price, surface_total) %>% 
  mutate(bedrooms = factor(bedrooms)) %>% 
  mutate(priceLog = log(price),
         surfaceLog = log(surface_total)) %>% 
  gather(key = "key", value = "valor", -c(bedrooms)) %>% 
  ggplot(aes(x = bedrooms, y = valor)) +
  facet_wrap(~key, scales = "free") +
  geom_boxplot(outlier.alpha = 0.01, fill = "#1C8356", alpha = 0.5,
               color = "#C4451C", size = 0.1) +
  stat_summary(fun.y = mean, geom = "point", color = "#C4451C", size = 2,
               shape = 17) +
  theme_fivethirtyeight() +
  labs(caption = "Triángulo = promedio", subtitle = "Dormitorios")

  • Distribución de precios y área por número de baños:
train %>% 
  select(bathrooms, price, surface_total) %>% 
  mutate(bathrooms = factor(bathrooms)) %>% 
  mutate(priceLog = log(price),
         surfaceLog = log(surface_total)) %>% 
  gather(key = "key", value = "valor", -c(bathrooms)) %>% 
  ggplot(aes(x = bathrooms, y = valor)) +
  facet_wrap(~key, scales = "free") +
  geom_boxplot(outlier.alpha = 0.01, fill = "#1C8356", alpha = 0.5,
               color = "#C4451C", size = 0.1) +
  stat_summary(fun.y = mean, geom = "point", color = "#C4451C", size = 2,
               shape = 17) +
  theme_fivethirtyeight() +
  labs(caption = "Triángulo = promedio", subtitle = "Baños")

Dispersiones

  • Relación general de área vs precio: como son más de 25 mil observaciones es preferible utilizar geom_bin2d() en lugar de geom_point().
train %>% 
  ggplot(aes(x = surface_total, y = price)) +
  geom_bin2d(color = "white", alpha = 0.8) +
  scale_fill_gradient2(low = "white", mid = "#1C8356", high = "#C4451C") +
  geom_smooth(method = "lm", color = "#C4451C", size = 2, se = FALSE) +
  theme_fivethirtyeight() +
  theme(legend.position = "right", legend.direction = "vertical")

NA

GLMNET

Train - Test

library(tidymodels)
set.seed(123)
datosTrain <- train %>% 
  select(-c(Id, property_type, operation_type, currency)) %>% 
  mutate(rooms = factor(rooms),
         bedrooms = factor(bedrooms),
         bathrooms = factor(bathrooms))
split_inicial <- initial_split(
                    data   = datosTrain,
                    prop   = 0.8,
                    strata = price
                 )
datos_train <- training(split_inicial)
datos_test  <- testing(split_inicial)

Modelo GLM - Tuning

# Modelo
mod_glm <- linear_reg(mode    = "regression",
                      penalty = tune(),
                      mixture = tune()) %>%
  set_engine(engine = "glmnet")

# Preprocesamiento
receta <- recipe(formula = price ~ .,
                 data =  datos_train) %>%
  step_center(all_numeric(), -all_outcomes()) %>%
  step_scale(all_numeric(), -all_outcomes()) %>%
  step_dummy(all_nominal(), -all_outcomes())

# Validación del modelo: validación cruzada K-folds con k = 10
set.seed(1992)
crossVal <- vfold_cv(data = datos_train,
                     v = 10,
                     strata = price)

# WORKFLOW
# =============================================================================
flujo_modelo <- workflow() %>%
  add_recipe(receta) %>%
  add_model(mod_glm)

# Grid de hiperparámetros
hiperpar_grid <- grid_regular(
  penalty(range = c(0, 1), trans = NULL),
  mixture(range = c(0, 1), trans = NULL),
  levels = c(10, 10))

# EJECUCIÓN DE LA OPTIMIZACIÓN DE HIPERPARÁMETROS
# =============================================================================
registerDoParallel(cores = parallel::detectCores() - 1)
myGrid <- tune_grid(
  object = flujo_modelo,
  resamples = crossVal,
  metrics = metric_set(rmse),
  control = control_resamples(save_pred = TRUE),
  grid = hiperpar_grid
)
stopImplicitCluster()
  • Mejores 10 modelos:
show_best(myGrid, metric = "rmse", n = 10)

Modelo GLM Final

mejorGrid <- select_best(myGrid, metric = "rmse")

flujo_final <- finalize_workflow(x = flujo_modelo, parameters = mejorGrid)


glm_final <-  flujo_final %>%
  fit(data = train)

Predichos GLM

predicciones <- glm_final %>%
  predict(new_data = datos_test,
          type = "numeric")
predicciones[is.na(predicciones)] <- 0
  • Error de test:
predicciones <- predicciones %>% 
                bind_cols(datos_test %>% select(price))

error_test_glm  <- rmse(
  data = predicciones,
  truth = price,
  estimate = .pred,
  na_rm = TRUE
) %>%
  mutate(modelo = "GLM")
error_test_glm

Predichos - Nuevos

prediccionesGLM_Subm1 <- glm_final %>%
  predict(new_data = test,
          type = "numeric")
prediccionesGLM_Subm1[is.na(prediccionesGLM_Subm1)] <- 0
prediccionesGLM_Subm1[prediccionesGLM_Subm1 < 0 ] <- 0
hist(prediccionesGLM_Subm1$.pred)

  • Submission 1:
subm1_glmnet <- data.frame(Id = sampleSub$Id,
                           price = prediccionesGLM_Subm1$.pred)
write.csv(subm1_glmnet, file = "Subm1.csv", row.names = FALSE)
  • Score: 2.72416885190957 - Posición 32.

Feature Engineering 1

  • Con la ciudad obtengo una nueva variable que informa si la ciudad es capital o no.
  • Obtengo una nueva variable en donde sumo las variables numéricas para cada fila.
  • Obtengo una nueva variable en donde promedio las variables numéricas para cada fila.
  • Obtengo una nueva variable para representar el tamaño de la casa en pequeña, mediana o grande.
# Capitales para Colombia y Argentina
capitales_colombia <- c("Armenia", "Barranquilla", "Bogotá D.C", "Bucaramanga",
                        "Cali", "Cartagena", "Ibagué", "Medellín", "Neiva",
                        "Popayán", "Santa Marta", "Tunja")
capitales_argentina <- c("La Plata", "Córdoba", "Corrientes", "Paraná",
                         "San Salvador de Jujuy", "Mendoza", "Posadas", "Neuquén",
                         "Salta", "San Juan", "San Luis", "Santa Fe", "San Miguel")
train %>% 
  mutate(rooms = as.integer(as.character(rooms)),
         bedrooms = as.integer(as.character(bedrooms)),
         bathrooms = as.integer(as.character(bathrooms))) %>% 
  mutate(Capital = if_else(pais == "Argentina" & ciudad %in% capitales_argentina,
                           true = "Si",
                           false = if_else(pais == "Colombia" & ciudad %in% capitales_colombia,
                                           true = "Si", false = "No")),
         sumaRow = rooms + bedrooms + bathrooms + surface_total,
         mediaRow = sumaRow/4) %>% 
   mutate(surfaceClass = if_else(surface_total <= 55, true = "Pequeña",
                                false = if_else(
                                  surface_total > 55 & surface_total <= 105,
                                  true = "Mediana",
                                  false = "Grande"
                                ))) ->
  newTrain1
newTrain1  
  • Test:
test %>% 
  mutate(rooms = as.integer(as.character(rooms)),
         bedrooms = as.integer(as.character(bedrooms)),
         bathrooms = as.integer(as.character(bathrooms))) %>% 
  mutate(Capital = if_else(pais == "Argentina" & ciudad %in% capitales_argentina,
                           true = "Si",
                           false = if_else(pais == "Colombia" & ciudad %in% capitales_colombia,
                                           true = "Si", false = "No")),
         sumaRow = rooms + bedrooms + bathrooms + surface_total,
         mediaRow = sumaRow/4) %>% 
   mutate(surfaceClass = if_else(surface_total <= 55, true = "Pequeña",
                                false = if_else(
                                  surface_total > 55 & surface_total <= 105,
                                  true = "Mediana",
                                  false = "Grande"
                                ))) ->
  newTest1
newTest1 

Exportando datos

save(newTrain1, file = "newTrain1.Rdata")
save(newTest1, file = "newTest1.Rdata")

Feature Engineering 2

  • Transformo las variables categóricas y en dummys (one hot encoding). La base de datos queda con 238 columnas.
  • Con estas variables realizo análisis de componentes principales y cluster.
# ------- Test ----
load("newTest1.Rdata")
test <- newTest1 %>% 
  mutate(rooms = factor(rooms),
         bedrooms = factor(bedrooms),
         bathrooms = factor(bathrooms)) %>% 
  mutate_if(is.character, as.factor) %>% 
  as.data.frame() 

test_dummy <- dummy_cols(test) %>% 
  select(-c(rooms, bedrooms, bathrooms, pais, provincia_departamento,
            ciudad, Capital, surfaceClass))

# ------- Train ----
load("newTrain1.Rdata")
train <- newTrain1 %>% 
  mutate(rooms = factor(rooms),
         bedrooms = factor(bedrooms),
         bathrooms = factor(bathrooms)) %>% 
  mutate_if(is.character, as.factor) %>% 
  relocate(price) %>% 
  as.data.frame()

train_dummy <- dummy_cols(train) %>% 
  select(-c(rooms, bedrooms, bathrooms, pais, provincia_departamento,
            ciudad, Capital, surfaceClass))

train_dummy2 <- train_dummy[, names(train_dummy) %in% names(test_dummy)]
train_dummy2$price <- train$price

test_dummy2 <-  test_dummy[, names(test_dummy) %in% names(train_dummy2)]
train_dummy2

ACP

library(FactoMineR)
library(factoextra)

acp <- PCA(X = train_dummy2 %>% select(-price),
           scale.unit = TRUE, ncp = 10, graph = FALSE)
summary(acp)
LS0tDQp0aXRsZTogIlByZWRpY2Npw7NuIGRlIHByZWNpbyBkZSBhcGFydGFtZW50b3MiDQpzdWJ0aXRsZTogIlJldG8gRGF0YVNvdXJjZSINCmF1dGhvcjogIltFZGltZXIgKFNpZGVyZXVzKV0oaHR0cHM6Ly9lZGltZXIuZ2l0aHViLmlvLykiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiANCiAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlDQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQogICAgdGhlbWU6IHNwYWNlbGFiDQogICAgY3NzOiBlc3RpbG8uY3NzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQotLS0NCg0KPGNlbnRlcj4NCjxpbWcgc3JjID0gIi4uL2ltZy9jb21wZXRlbmNpYS5wbmciIC8+DQo8L2NlbnRlcj4NCg0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ24gPSAiY2VudGVyIikNCmBgYA0KDQotIFtTaXRpbyBvZmljaWFsIGRlbCByZXRvIGVuIERhdGFTb3VyY2UuXShodHRwczovL3d3dy5kYXRhc291cmNlLmFpL2VzL2hvbWUvY29tcGV0aXRpb25zL3ByZWRpY2Npb24tZGUtcHJlY2lvcy1kZS1hcGFydGFtZW50b3MtZW4tYXJnZW50aW5hLXktY29sb21iaWEpDQoNCiMgVmFyaWFibGVzDQoNCjxjZW50ZXI+DQo8aW1nIHNyYyA9ICIuLi9pbWcvdmFyaWFibGVzLnBuZyIgLz4NCjwvY2VudGVyPg0KDQojIERhdG9zIEluaWNpYWxlcyB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30NCg0KIyMgVHJhaW4NCg0KYGBge3J9DQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCnRyYWluIDwtIGZyZWFkKCIuLi9kYXRhL3RyYWluLmNzdiIsIGVuY29kaW5nID0gIlVURi04IikgJT4lIA0KICBzZWxlY3QoLWMoSWQsIHByb3BlcnR5X3R5cGUsIG9wZXJhdGlvbl90eXBlLCBjdXJyZW5jeSkpICU+JSANCiAgbXV0YXRlKHJvb21zID0gZmFjdG9yKHJvb21zKSwNCiAgICAgICAgIGJlZHJvb21zID0gZmFjdG9yKGJlZHJvb21zKSwNCiAgICAgICAgIGJhdGhyb29tcyA9IGZhY3RvcihiYXRocm9vbXMpKQ0KaGVhZCh0cmFpbikNCmBgYA0KDQojIyBUZXN0DQoNCmBgYHtyfQ0KdGVzdCA8LSBmcmVhZCgiLi4vZGF0YS90ZXN0LmNzdiIsIGVuY29kaW5nID0gIlVURi04IikgJT4lIA0KICBzZWxlY3QoLWMoSWQsIHByb3BlcnR5X3R5cGUsIG9wZXJhdGlvbl90eXBlLCBjdXJyZW5jeSkpICU+JSANCiAgbXV0YXRlKHJvb21zID0gZmFjdG9yKHJvb21zKSwNCiAgICAgICAgIGJlZHJvb21zID0gZmFjdG9yKGJlZHJvb21zKSwNCiAgICAgICAgIGJhdGhyb29tcyA9IGZhY3RvcihiYXRocm9vbXMpKQ0KaGVhZCh0ZXN0KQ0KYGBgDQoNCiMjIFNhbXBsZSBTdWJtaXNzaW9uDQoNCmBgYHtyfQ0Kc2FtcGxlU3ViIDwtIGZyZWFkKCIuLi9kYXRhL3NhbXBsZVN1Yi5jc3YiLCBlbmNvZGluZyA9ICJVVEYtOCIpDQpoZWFkKHNhbXBsZVN1YikNCmBgYA0KIyBFeHBsb3JhdG9yaW8gVHJhaW4gey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9DQoNCiMjIFRhbWHDsW8gbXVlc3RyYWwNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkodHJlZW1hcCkNCnRyYWluICU+JSANCiAgZ3JvdXBfYnkocGFpcywgcHJvdmluY2lhX2RlcGFydGFtZW50bykgJT4lIA0KICBjb3VudChuYW1lID0gInRvdGFsIikgJT4lIA0KICB0cmVlbWFwKC4sDQogICAgICAgIGluZGV4ID0gYygicGFpcyIsInByb3ZpbmNpYV9kZXBhcnRhbWVudG8iKSwNCiAgICAgICAgdlNpemUgPSAidG90YWwiLCANCiAgICAgICAgdHlwZSA9ICJpbmRleCIsIA0KICAgICAgICBwYWxldHRlID0gYygiIzFDODM1NiIsICIjQzQ0NTFDIiksDQogICAgICAgIHRpdGxlID0gIlRhbWHDsW8gbXVlc3RyYWw6IFBhw61zIC0gRGVwYXJ0YW1lbnRvIiwgICANCiAgICAgICAgZm9udHNpemUudGl0bGUgPSAxMg0KIA0KKQ0KDQogICAgIA0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdHJhaW4gJT4lIA0KICBncm91cF9ieShwYWlzLCByb29tcykgJT4lIA0KICBjb3VudChuYW1lID0gInRvdGFsIikgJT4lIA0KICB0cmVlbWFwKC4sDQogICAgICAgIGluZGV4ID0gYygicGFpcyIsInJvb21zIiksDQogICAgICAgIHZTaXplID0gInRvdGFsIiwgDQogICAgICAgIHR5cGUgPSAiaW5kZXgiLCANCiAgICAgICAgcGFsZXR0ZSA9IGMoIiMxQzgzNTYiLCAiI0M0NDUxQyIpLA0KICAgICAgICB0aXRsZSA9ICJUYW1hw7FvIG11ZXN0cmFsOiBQYcOtcyAtICMgZGUgU2FsYXMiLCAgIA0KICAgICAgICBmb250c2l6ZS50aXRsZSA9IDEyDQogDQopDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0cmFpbiAlPiUgDQogIGdyb3VwX2J5KHBhaXMsIGJlZHJvb21zKSAlPiUgDQogIGNvdW50KG5hbWUgPSAidG90YWwiKSAlPiUgDQogIHRyZWVtYXAoLiwNCiAgICAgICAgaW5kZXggPSBjKCJwYWlzIiwiYmVkcm9vbXMiKSwNCiAgICAgICAgdlNpemUgPSAidG90YWwiLCANCiAgICAgICAgdHlwZSA9ICJpbmRleCIsIA0KICAgICAgICBwYWxldHRlID0gYygiIzFDODM1NiIsICIjQzQ0NTFDIiksDQogICAgICAgIHRpdGxlID0gIlRhbWHDsW8gbXVlc3RyYWw6IFBhw61zIC0gIyBkZSBEb3JtaXRvcmlvcyIsICAgDQogICAgICAgIGZvbnRzaXplLnRpdGxlID0gMTINCiANCikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnRyYWluICU+JSANCiAgZ3JvdXBfYnkocGFpcywgYmF0aHJvb21zKSAlPiUgDQogIGNvdW50KG5hbWUgPSAidG90YWwiKSAlPiUgDQogIHRyZWVtYXAoLiwNCiAgICAgICAgaW5kZXggPSBjKCJwYWlzIiwiYmF0aHJvb21zIiksDQogICAgICAgIHZTaXplID0gInRvdGFsIiwgDQogICAgICAgIHR5cGUgPSAiaW5kZXgiLCANCiAgICAgICAgcGFsZXR0ZSA9IGMoIiMxQzgzNTYiLCAiI0M0NDUxQyIpLA0KICAgICAgICB0aXRsZSA9ICJUYW1hw7FvIG11ZXN0cmFsOiBQYcOtcyAtICMgZGUgQmHDsW9zIiwgICANCiAgICAgICAgZm9udHNpemUudGl0bGUgPSAxMg0KIA0KKQ0KYGBgDQoNCiMjIERpc3RyaWJ1Y2lvbmVzDQoNCi0gKipCYcOxb3MsIERvcm1pdG9yaW9zIHkgU2FsYXM6KioNCg0KYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KGdndGhlbWVzKQ0KdHJhaW4gJT4lIA0KICBzZWxlY3Qocm9vbXMsIGJlZHJvb21zLCBiYXRocm9vbXMpICU+JSANCiAgZ2F0aGVyKCkgJT4lIA0KICBncm91cF9ieShrZXksIHZhbHVlKSAlPiUgDQogIGNvdW50KG5hbWUgPSAiVG90YWwiKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHZhbHVlLCB5ID0gVG90YWwpKSArDQogIGZhY2V0X3dyYXAofmtleSwgc2NhbGVzID0gImZyZWUiKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIGNvbG9yID0gIiNDNDQ1MUMiKSArDQogIGdlb21fc2VnbWVudChhZXMoeSA9IDAsIHhlbmQgPSB2YWx1ZSwgeWVuZCA9IFRvdGFsKSwgY29sb3IgPSAiIzFDODM1NiIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKG4uYnJlYWtzID0gMTApICsNCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkNCg0KYGBgDQoNCi0gKipQcmVjaW8geSDDoXJlYSBlbiBlc2NhbGEgb3JpZ2luYWwgeSBsb2dhcsOtdG1pY2E6KioNCg0KYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQp0cmFpbiAlPiUgDQogIHNlbGVjdChwcmljZSwgc3VyZmFjZV90b3RhbCkgJT4lIA0KICBtdXRhdGUocHJpY2VMb2cgPSBsb2cocHJpY2UpLA0KICAgICAgICAgc3VyZmFjZUxvZyA9IGxvZyhzdXJmYWNlX3RvdGFsKSkgJT4lIA0KICBnYXRoZXIoKSAlPiUgDQogIGdyb3VwX2J5KGtleSkgJT4lIA0KICBzdW1tYXJpc2UobWVkaWEgPSBtZWFuKHZhbHVlLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgZGUgPSBzZCh2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBtdXRhdGUobWVkaWFfbWFzXzFERSA9IG1lZGlhICsgZGUsDQogICAgICAgICBtZWRpYV9tZW5vc18xREUgPSBtZWRpYSAtIGRlKS0+DQogIG1lZGlhcw0KDQp0cmFpbiAlPiUgDQogIHNlbGVjdChwcmljZSwgc3VyZmFjZV90b3RhbCkgJT4lIA0KICBtdXRhdGUocHJpY2VMb2cgPSBsb2cocHJpY2UpLA0KICAgICAgICAgc3VyZmFjZUxvZyA9IGxvZyhzdXJmYWNlX3RvdGFsKSkgJT4lIA0KICBnYXRoZXIoKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHZhbHVlKSkgKw0KICBmYWNldF93cmFwKH5rZXksIHNjYWxlcyA9ICJmcmVlIikgKw0KICBnZW9tX2RlbnNpdHkoc2l6ZSA9IDAuNSwgY29sb3IgPSAiI0M0NDUxQyIsIGZpbGwgPSAiIzFDODM1NiIsIGFscGhhID0gMC41KSArDQogIGdlb21fdmxpbmUoZGF0YSA9IG1lZGlhcywgYWVzKHhpbnRlcmNlcHQgPSBtZWRpYSksDQogICAgICAgICAgICAgY29sb3IgPSAiI0M0NDUxQyIsIHNpemUgPSAxKSArDQogIGdlb21fdmxpbmUoZGF0YSA9IG1lZGlhcywgYWVzKHhpbnRlcmNlcHQgPSBtZWRpYV9tYXNfMURFKSwNCiAgICAgICAgICAgICBjb2xvciA9ICIjMUM4MzU2Iiwgc2l6ZSA9IDEsIGx0eSA9IDIpICsNCiAgZ2VvbV92bGluZShkYXRhID0gbWVkaWFzLCBhZXMoeGludGVyY2VwdCA9IG1lZGlhX21lbm9zXzFERSksDQogICAgICAgICAgICAgY29sb3IgPSAiIzFDODM1NiIsIHNpemUgPSAxLCBsdHkgPSAyKSArDQogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsNCiAgbGFicyhjYXB0aW9uID0gIkzDrW5lYSBzw7NsaWRhOiBwcm9tZWRpb1xuTMOtbmVhIHB1bnRlYWRhOiArMSB5IC0xIERFIikNCg0KYGBgDQoNCiMjIENvbXBhcmF0aXZvcw0KDQotICoqRGlzdHJpYnVjacOzbiBkZSBwcmVjaW9zIHkgw6FyZWEgcG9yIG7Dum1lcm8gZGUgaGFiaXRhY2lvbmVzOioqDQoNCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD01LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KdHJhaW4gJT4lIA0KICBzZWxlY3Qocm9vbXMsIHByaWNlLCBzdXJmYWNlX3RvdGFsKSAlPiUgDQogIG11dGF0ZShyb29tcyA9IGZhY3Rvcihyb29tcykpICU+JSANCiAgbXV0YXRlKHByaWNlTG9nID0gbG9nKHByaWNlKSwNCiAgICAgICAgIHN1cmZhY2VMb2cgPSBsb2coc3VyZmFjZV90b3RhbCkpICU+JSANCiAgZ2F0aGVyKGtleSA9ICJrZXkiLCB2YWx1ZSA9ICJ2YWxvciIsIC1jKHJvb21zKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSByb29tcywgeSA9IHZhbG9yKSkgKw0KICBmYWNldF93cmFwKH5rZXksIHNjYWxlcyA9ICJmcmVlIikgKw0KICBnZW9tX2JveHBsb3Qob3V0bGllci5hbHBoYSA9IDAuMDEsIGZpbGwgPSAiIzFDODM1NiIsIGFscGhhID0gMC41LA0KICAgICAgICAgICAgICAgY29sb3IgPSAiI0M0NDUxQyIsIHNpemUgPSAwLjEpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIGNvbG9yID0gIiNDNDQ1MUMiLCBzaXplID0gMiwNCiAgICAgICAgICAgICAgIHNoYXBlID0gMTcpICsNCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKw0KICBsYWJzKGNhcHRpb24gPSAiVHJpw6FuZ3VsbyA9IHByb21lZGlvIiwgc3VidGl0bGUgPSAiSGFiaXRhY2lvbmVzIikNCg0KYGBgDQoNCi0gKipEaXN0cmlidWNpw7NuIGRlIHByZWNpb3MgeSDDoXJlYSBwb3IgbsO6bWVybyBkZSBkb3JtaXRvcmlvczoqKg0KICANCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD01LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KdHJhaW4gJT4lIA0KICBzZWxlY3QoYmVkcm9vbXMsIHByaWNlLCBzdXJmYWNlX3RvdGFsKSAlPiUgDQogIG11dGF0ZShiZWRyb29tcyA9IGZhY3RvcihiZWRyb29tcykpICU+JSANCiAgbXV0YXRlKHByaWNlTG9nID0gbG9nKHByaWNlKSwNCiAgICAgICAgIHN1cmZhY2VMb2cgPSBsb2coc3VyZmFjZV90b3RhbCkpICU+JSANCiAgZ2F0aGVyKGtleSA9ICJrZXkiLCB2YWx1ZSA9ICJ2YWxvciIsIC1jKGJlZHJvb21zKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBiZWRyb29tcywgeSA9IHZhbG9yKSkgKw0KICBmYWNldF93cmFwKH5rZXksIHNjYWxlcyA9ICJmcmVlIikgKw0KICBnZW9tX2JveHBsb3Qob3V0bGllci5hbHBoYSA9IDAuMDEsIGZpbGwgPSAiIzFDODM1NiIsIGFscGhhID0gMC41LA0KICAgICAgICAgICAgICAgY29sb3IgPSAiI0M0NDUxQyIsIHNpemUgPSAwLjEpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIGNvbG9yID0gIiNDNDQ1MUMiLCBzaXplID0gMiwNCiAgICAgICAgICAgICAgIHNoYXBlID0gMTcpICsNCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKw0KICBsYWJzKGNhcHRpb24gPSAiVHJpw6FuZ3VsbyA9IHByb21lZGlvIiwgc3VidGl0bGUgPSAiRG9ybWl0b3Jpb3MiKQ0KDQpgYGANCg0KLSAqKkRpc3RyaWJ1Y2nDs24gZGUgcHJlY2lvcyB5IMOhcmVhIHBvciBuw7ptZXJvIGRlIGJhw7FvczoqKg0KICANCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD01LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KdHJhaW4gJT4lIA0KICBzZWxlY3QoYmF0aHJvb21zLCBwcmljZSwgc3VyZmFjZV90b3RhbCkgJT4lIA0KICBtdXRhdGUoYmF0aHJvb21zID0gZmFjdG9yKGJhdGhyb29tcykpICU+JSANCiAgbXV0YXRlKHByaWNlTG9nID0gbG9nKHByaWNlKSwNCiAgICAgICAgIHN1cmZhY2VMb2cgPSBsb2coc3VyZmFjZV90b3RhbCkpICU+JSANCiAgZ2F0aGVyKGtleSA9ICJrZXkiLCB2YWx1ZSA9ICJ2YWxvciIsIC1jKGJhdGhyb29tcykpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gYmF0aHJvb21zLCB5ID0gdmFsb3IpKSArDQogIGZhY2V0X3dyYXAofmtleSwgc2NhbGVzID0gImZyZWUiKSArDQogIGdlb21fYm94cGxvdChvdXRsaWVyLmFscGhhID0gMC4wMSwgZmlsbCA9ICIjMUM4MzU2IiwgYWxwaGEgPSAwLjUsDQogICAgICAgICAgICAgICBjb2xvciA9ICIjQzQ0NTFDIiwgc2l6ZSA9IDAuMSkgKw0KICBzdGF0X3N1bW1hcnkoZnVuLnkgPSBtZWFuLCBnZW9tID0gInBvaW50IiwgY29sb3IgPSAiI0M0NDUxQyIsIHNpemUgPSAyLA0KICAgICAgICAgICAgICAgc2hhcGUgPSAxNykgKw0KICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKSArDQogIGxhYnMoY2FwdGlvbiA9ICJUcmnDoW5ndWxvID0gcHJvbWVkaW8iLCBzdWJ0aXRsZSA9ICJCYcOxb3MiKQ0KDQpgYGANCg0KIyMgRGlzcGVyc2lvbmVzDQoNCi0gKipSZWxhY2nDs24gZ2VuZXJhbCBkZSDDoXJlYSB2cyBwcmVjaW86KiogY29tbyBzb24gbcOhcyBkZSAyNSBtaWwgb2JzZXJ2YWNpb25lcyBlcyBwcmVmZXJpYmxlIHV0aWxpemFyIGBnZW9tX2JpbjJkKClgIGVuIGx1Z2FyIGRlIGBnZW9tX3BvaW50KClgLg0KDQpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnRyYWluICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gc3VyZmFjZV90b3RhbCwgeSA9IHByaWNlKSkgKw0KICBnZW9tX2JpbjJkKGNvbG9yID0gIndoaXRlIiwgYWxwaGEgPSAwLjgpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gIndoaXRlIiwgbWlkID0gIiMxQzgzNTYiLCBoaWdoID0gIiNDNDQ1MUMiKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbG9yID0gIiNDNDQ1MUMiLCBzaXplID0gMiwgc2UgPSBGQUxTRSkgKw0KICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsIGxlZ2VuZC5kaXJlY3Rpb24gPSAidmVydGljYWwiKQ0KICANCmBgYA0KDQojIEdMTU5FVCB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30NCg0KIyMgVHJhaW4gLSBUZXN0DQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5bW9kZWxzKQ0Kc2V0LnNlZWQoMTIzKQ0KZGF0b3NUcmFpbiA8LSB0cmFpbiAlPiUgDQogIHNlbGVjdCgtYyhJZCwgcHJvcGVydHlfdHlwZSwgb3BlcmF0aW9uX3R5cGUsIGN1cnJlbmN5KSkgJT4lIA0KICBtdXRhdGUocm9vbXMgPSBmYWN0b3Iocm9vbXMpLA0KICAgICAgICAgYmVkcm9vbXMgPSBmYWN0b3IoYmVkcm9vbXMpLA0KICAgICAgICAgYmF0aHJvb21zID0gZmFjdG9yKGJhdGhyb29tcykpDQpzcGxpdF9pbmljaWFsIDwtIGluaXRpYWxfc3BsaXQoDQogICAgICAgICAgICAgICAgICAgIGRhdGEgICA9IGRhdG9zVHJhaW4sDQogICAgICAgICAgICAgICAgICAgIHByb3AgICA9IDAuOCwNCiAgICAgICAgICAgICAgICAgICAgc3RyYXRhID0gcHJpY2UNCiAgICAgICAgICAgICAgICAgKQ0KZGF0b3NfdHJhaW4gPC0gdHJhaW5pbmcoc3BsaXRfaW5pY2lhbCkNCmRhdG9zX3Rlc3QgIDwtIHRlc3Rpbmcoc3BsaXRfaW5pY2lhbCkNCmBgYA0KDQojIyBNb2RlbG8gR0xNIC0gVHVuaW5nDQoNCmBgYHtyfQ0KIyBNb2RlbG8NCm1vZF9nbG0gPC0gbGluZWFyX3JlZyhtb2RlICAgID0gInJlZ3Jlc3Npb24iLA0KICAgICAgICAgICAgICAgICAgICAgIHBlbmFsdHkgPSB0dW5lKCksDQogICAgICAgICAgICAgICAgICAgICAgbWl4dHVyZSA9IHR1bmUoKSkgJT4lDQogIHNldF9lbmdpbmUoZW5naW5lID0gImdsbW5ldCIpDQoNCiMgUHJlcHJvY2VzYW1pZW50bw0KcmVjZXRhIDwtIHJlY2lwZShmb3JtdWxhID0gcHJpY2UgfiAuLA0KICAgICAgICAgICAgICAgICBkYXRhID0gIGRhdG9zX3RyYWluKSAlPiUNCiAgc3RlcF9jZW50ZXIoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUNCiAgc3RlcF9zY2FsZShhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpICU+JQ0KICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSkNCg0KIyBWYWxpZGFjacOzbiBkZWwgbW9kZWxvOiB2YWxpZGFjacOzbiBjcnV6YWRhIEstZm9sZHMgY29uIGsgPSAxMA0Kc2V0LnNlZWQoMTk5MikNCmNyb3NzVmFsIDwtIHZmb2xkX2N2KGRhdGEgPSBkYXRvc190cmFpbiwNCiAgICAgICAgICAgICAgICAgICAgIHYgPSAxMCwNCiAgICAgICAgICAgICAgICAgICAgIHN0cmF0YSA9IHByaWNlKQ0KDQojIFdPUktGTE9XDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQpmbHVqb19tb2RlbG8gPC0gd29ya2Zsb3coKSAlPiUNCiAgYWRkX3JlY2lwZShyZWNldGEpICU+JQ0KICBhZGRfbW9kZWwobW9kX2dsbSkNCg0KIyBHcmlkIGRlIGhpcGVycGFyw6FtZXRyb3MNCmhpcGVycGFyX2dyaWQgPC0gZ3JpZF9yZWd1bGFyKA0KICBwZW5hbHR5KHJhbmdlID0gYygwLCAxKSwgdHJhbnMgPSBOVUxMKSwNCiAgbWl4dHVyZShyYW5nZSA9IGMoMCwgMSksIHRyYW5zID0gTlVMTCksDQogIGxldmVscyA9IGMoMTAsIDEwKSkNCg0KIyBFSkVDVUNJw5NOIERFIExBIE9QVElNSVpBQ0nDk04gREUgSElQRVJQQVLDgU1FVFJPUw0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KcmVnaXN0ZXJEb1BhcmFsbGVsKGNvcmVzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkgLSAxKQ0KbXlHcmlkIDwtIHR1bmVfZ3JpZCgNCiAgb2JqZWN0ID0gZmx1am9fbW9kZWxvLA0KICByZXNhbXBsZXMgPSBjcm9zc1ZhbCwNCiAgbWV0cmljcyA9IG1ldHJpY19zZXQocm1zZSksDQogIGNvbnRyb2wgPSBjb250cm9sX3Jlc2FtcGxlcyhzYXZlX3ByZWQgPSBUUlVFKSwNCiAgZ3JpZCA9IGhpcGVycGFyX2dyaWQNCikNCnN0b3BJbXBsaWNpdENsdXN0ZXIoKQ0KYGBgDQoNCi0gKipNZWpvcmVzIDEwIG1vZGVsb3M6KioNCg0KYGBge3J9DQpzaG93X2Jlc3QobXlHcmlkLCBtZXRyaWMgPSAicm1zZSIsIG4gPSAxMCkNCmBgYA0KDQojIyBNb2RlbG8gR0xNIEZpbmFsDQoNCmBgYHtyfQ0KbWVqb3JHcmlkIDwtIHNlbGVjdF9iZXN0KG15R3JpZCwgbWV0cmljID0gInJtc2UiKQ0KDQpmbHVqb19maW5hbCA8LSBmaW5hbGl6ZV93b3JrZmxvdyh4ID0gZmx1am9fbW9kZWxvLCBwYXJhbWV0ZXJzID0gbWVqb3JHcmlkKQ0KDQoNCmdsbV9maW5hbCA8LSAgZmx1am9fZmluYWwgJT4lDQogIGZpdChkYXRhID0gdHJhaW4pDQpgYGANCg0KIyMgUHJlZGljaG9zIEdMTQ0KDQpgYGB7cn0NCnByZWRpY2Npb25lcyA8LSBnbG1fZmluYWwgJT4lDQogIHByZWRpY3QobmV3X2RhdGEgPSBkYXRvc190ZXN0LA0KICAgICAgICAgIHR5cGUgPSAibnVtZXJpYyIpDQpwcmVkaWNjaW9uZXNbaXMubmEocHJlZGljY2lvbmVzKV0gPC0gMA0KYGBgDQoNCi0gKipFcnJvciBkZSB0ZXN0OioqDQoNCmBgYHtyfQ0KcHJlZGljY2lvbmVzIDwtIHByZWRpY2Npb25lcyAlPiUgDQogICAgICAgICAgICAgICAgYmluZF9jb2xzKGRhdG9zX3Rlc3QgJT4lIHNlbGVjdChwcmljZSkpDQoNCmVycm9yX3Rlc3RfZ2xtICA8LSBybXNlKA0KICBkYXRhID0gcHJlZGljY2lvbmVzLA0KICB0cnV0aCA9IHByaWNlLA0KICBlc3RpbWF0ZSA9IC5wcmVkLA0KICBuYV9ybSA9IFRSVUUNCikgJT4lDQogIG11dGF0ZShtb2RlbG8gPSAiR0xNIikNCmVycm9yX3Rlc3RfZ2xtDQpgYGANCg0KIyMgUHJlZGljaG9zIC0gTnVldm9zDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KcHJlZGljY2lvbmVzR0xNX1N1Ym0xIDwtIGdsbV9maW5hbCAlPiUNCiAgcHJlZGljdChuZXdfZGF0YSA9IHRlc3QsDQogICAgICAgICAgdHlwZSA9ICJudW1lcmljIikNCnByZWRpY2Npb25lc0dMTV9TdWJtMVtpcy5uYShwcmVkaWNjaW9uZXNHTE1fU3VibTEpXSA8LSAwDQpwcmVkaWNjaW9uZXNHTE1fU3VibTFbcHJlZGljY2lvbmVzR0xNX1N1Ym0xIDwgMCBdIDwtIDANCmhpc3QocHJlZGljY2lvbmVzR0xNX1N1Ym0xJC5wcmVkKQ0KYGBgDQoNCi0gKipTdWJtaXNzaW9uIDE6KioNCg0KYGBge3J9DQpzdWJtMV9nbG1uZXQgPC0gZGF0YS5mcmFtZShJZCA9IHNhbXBsZVN1YiRJZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaWNlID0gcHJlZGljY2lvbmVzR0xNX1N1Ym0xJC5wcmVkKQ0Kd3JpdGUuY3N2KHN1Ym0xX2dsbW5ldCwgZmlsZSA9ICJTdWJtMS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQotICoqU2NvcmU6KiogMi43MjQxNjg4NTE5MDk1NyAtIFBvc2ljacOzbiAzMi4NCg0KIyBGZWF0dXJlIEVuZ2luZWVyaW5nIDENCg0KLSBDb24gbGEgY2l1ZGFkIG9idGVuZ28gdW5hIG51ZXZhIHZhcmlhYmxlIHF1ZSBpbmZvcm1hIHNpIGxhIGNpdWRhZCBlcyBjYXBpdGFsIG8gbm8uDQotIE9idGVuZ28gdW5hIG51ZXZhIHZhcmlhYmxlIGVuIGRvbmRlIHN1bW8gbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzIHBhcmEgY2FkYSBmaWxhLg0KLSBPYnRlbmdvIHVuYSBudWV2YSB2YXJpYWJsZSBlbiBkb25kZSBwcm9tZWRpbyBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMgcGFyYSBjYWRhIGZpbGEuDQotIE9idGVuZ28gdW5hIG51ZXZhIHZhcmlhYmxlIHBhcmEgcmVwcmVzZW50YXIgZWwgdGFtYcOxbyBkZSBsYSBjYXNhIGVuIHBlcXVlw7FhLCBtZWRpYW5hIG8gZ3JhbmRlLg0KDQpgYGB7cn0NCiMgQ2FwaXRhbGVzIHBhcmEgQ29sb21iaWEgeSBBcmdlbnRpbmENCmNhcGl0YWxlc19jb2xvbWJpYSA8LSBjKCJBcm1lbmlhIiwgIkJhcnJhbnF1aWxsYSIsICJCb2dvdMOhIEQuQyIsICJCdWNhcmFtYW5nYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiQ2FsaSIsICJDYXJ0YWdlbmEiLCAiSWJhZ3XDqSIsICJNZWRlbGzDrW4iLCAiTmVpdmEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIlBvcGF5w6FuIiwgIlNhbnRhIE1hcnRhIiwgIlR1bmphIikNCmNhcGl0YWxlc19hcmdlbnRpbmEgPC0gYygiTGEgUGxhdGEiLCAiQ8OzcmRvYmEiLCAiQ29ycmllbnRlcyIsICJQYXJhbsOhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiU2FuIFNhbHZhZG9yIGRlIEp1anV5IiwgIk1lbmRvemEiLCAiUG9zYWRhcyIsICJOZXVxdcOpbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIlNhbHRhIiwgIlNhbiBKdWFuIiwgIlNhbiBMdWlzIiwgIlNhbnRhIEZlIiwgIlNhbiBNaWd1ZWwiKQ0KdHJhaW4gJT4lIA0KICBtdXRhdGUocm9vbXMgPSBhcy5pbnRlZ2VyKGFzLmNoYXJhY3Rlcihyb29tcykpLA0KICAgICAgICAgYmVkcm9vbXMgPSBhcy5pbnRlZ2VyKGFzLmNoYXJhY3RlcihiZWRyb29tcykpLA0KICAgICAgICAgYmF0aHJvb21zID0gYXMuaW50ZWdlcihhcy5jaGFyYWN0ZXIoYmF0aHJvb21zKSkpICU+JSANCiAgbXV0YXRlKENhcGl0YWwgPSBpZl9lbHNlKHBhaXMgPT0gIkFyZ2VudGluYSIgJiBjaXVkYWQgJWluJSBjYXBpdGFsZXNfYXJnZW50aW5hLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSA9ICJTaSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBmYWxzZSA9IGlmX2Vsc2UocGFpcyA9PSAiQ29sb21iaWEiICYgY2l1ZGFkICVpbiUgY2FwaXRhbGVzX2NvbG9tYmlhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydWUgPSAiU2kiLCBmYWxzZSA9ICJObyIpKSwNCiAgICAgICAgIHN1bWFSb3cgPSByb29tcyArIGJlZHJvb21zICsgYmF0aHJvb21zICsgc3VyZmFjZV90b3RhbCwNCiAgICAgICAgIG1lZGlhUm93ID0gc3VtYVJvdy80KSAlPiUgDQogICBtdXRhdGUoc3VyZmFjZUNsYXNzID0gaWZfZWxzZShzdXJmYWNlX3RvdGFsIDw9IDU1LCB0cnVlID0gIlBlcXVlw7FhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFsc2UgPSBpZl9lbHNlKA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1cmZhY2VfdG90YWwgPiA1NSAmIHN1cmZhY2VfdG90YWwgPD0gMTA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydWUgPSAiTWVkaWFuYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFsc2UgPSAiR3JhbmRlIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSkgLT4NCiAgbmV3VHJhaW4xDQpuZXdUcmFpbjEgIA0KYGBgDQoNCi0gKipUZXN0OioqDQoNCmBgYHtyfQ0KdGVzdCAlPiUgDQogIG11dGF0ZShyb29tcyA9IGFzLmludGVnZXIoYXMuY2hhcmFjdGVyKHJvb21zKSksDQogICAgICAgICBiZWRyb29tcyA9IGFzLmludGVnZXIoYXMuY2hhcmFjdGVyKGJlZHJvb21zKSksDQogICAgICAgICBiYXRocm9vbXMgPSBhcy5pbnRlZ2VyKGFzLmNoYXJhY3RlcihiYXRocm9vbXMpKSkgJT4lIA0KICBtdXRhdGUoQ2FwaXRhbCA9IGlmX2Vsc2UocGFpcyA9PSAiQXJnZW50aW5hIiAmIGNpdWRhZCAlaW4lIGNhcGl0YWxlc19hcmdlbnRpbmEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICB0cnVlID0gIlNpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhbHNlID0gaWZfZWxzZShwYWlzID09ICJDb2xvbWJpYSIgJiBjaXVkYWQgJWluJSBjYXBpdGFsZXNfY29sb21iaWEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSA9ICJTaSIsIGZhbHNlID0gIk5vIikpLA0KICAgICAgICAgc3VtYVJvdyA9IHJvb21zICsgYmVkcm9vbXMgKyBiYXRocm9vbXMgKyBzdXJmYWNlX3RvdGFsLA0KICAgICAgICAgbWVkaWFSb3cgPSBzdW1hUm93LzQpICU+JSANCiAgIG11dGF0ZShzdXJmYWNlQ2xhc3MgPSBpZl9lbHNlKHN1cmZhY2VfdG90YWwgPD0gNTUsIHRydWUgPSAiUGVxdWXDsWEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYWxzZSA9IGlmX2Vsc2UoDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VyZmFjZV90b3RhbCA+IDU1ICYgc3VyZmFjZV90b3RhbCA8PSAxMDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSA9ICJNZWRpYW5hIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYWxzZSA9ICJHcmFuZGUiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpKSAtPg0KICBuZXdUZXN0MQ0KbmV3VGVzdDEgDQpgYGANCg0KIyMgRXhwb3J0YW5kbyBkYXRvcw0KDQpgYGB7cn0NCnNhdmUobmV3VHJhaW4xLCBmaWxlID0gIm5ld1RyYWluMS5SZGF0YSIpDQpzYXZlKG5ld1Rlc3QxLCBmaWxlID0gIm5ld1Rlc3QxLlJkYXRhIikNCmBgYA0KDQojIEZlYXR1cmUgRW5naW5lZXJpbmcgMg0KDQotIFRyYW5zZm9ybW8gbGFzIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMgeSBlbiBkdW1teXMgKG9uZSBob3QgZW5jb2RpbmcpLiBMYSBiYXNlIGRlIGRhdG9zIHF1ZWRhIGNvbiAyMzggY29sdW1uYXMuDQotIENvbiBlc3RhcyB2YXJpYWJsZXMgcmVhbGl6byBhbsOhbGlzaXMgZGUgY29tcG9uZW50ZXMgcHJpbmNpcGFsZXMgeSBjbHVzdGVyLg0KDQpgYGB7cn0NCiMgLS0tLS0tLSBUZXN0IC0tLS0NCmxvYWQoIm5ld1Rlc3QxLlJkYXRhIikNCnRlc3QgPC0gbmV3VGVzdDEgJT4lIA0KICBtdXRhdGUocm9vbXMgPSBmYWN0b3Iocm9vbXMpLA0KICAgICAgICAgYmVkcm9vbXMgPSBmYWN0b3IoYmVkcm9vbXMpLA0KICAgICAgICAgYmF0aHJvb21zID0gZmFjdG9yKGJhdGhyb29tcykpICU+JSANCiAgbXV0YXRlX2lmKGlzLmNoYXJhY3RlciwgYXMuZmFjdG9yKSAlPiUgDQogIGFzLmRhdGEuZnJhbWUoKSANCg0KdGVzdF9kdW1teSA8LSBkdW1teV9jb2xzKHRlc3QpICU+JSANCiAgc2VsZWN0KC1jKHJvb21zLCBiZWRyb29tcywgYmF0aHJvb21zLCBwYWlzLCBwcm92aW5jaWFfZGVwYXJ0YW1lbnRvLA0KICAgICAgICAgICAgY2l1ZGFkLCBDYXBpdGFsLCBzdXJmYWNlQ2xhc3MpKQ0KDQojIC0tLS0tLS0gVHJhaW4gLS0tLQ0KbG9hZCgibmV3VHJhaW4xLlJkYXRhIikNCnRyYWluIDwtIG5ld1RyYWluMSAlPiUgDQogIG11dGF0ZShyb29tcyA9IGZhY3Rvcihyb29tcyksDQogICAgICAgICBiZWRyb29tcyA9IGZhY3RvcihiZWRyb29tcyksDQogICAgICAgICBiYXRocm9vbXMgPSBmYWN0b3IoYmF0aHJvb21zKSkgJT4lIA0KICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCBhcy5mYWN0b3IpICU+JSANCiAgcmVsb2NhdGUocHJpY2UpICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpDQoNCnRyYWluX2R1bW15IDwtIGR1bW15X2NvbHModHJhaW4pICU+JSANCiAgc2VsZWN0KC1jKHJvb21zLCBiZWRyb29tcywgYmF0aHJvb21zLCBwYWlzLCBwcm92aW5jaWFfZGVwYXJ0YW1lbnRvLA0KICAgICAgICAgICAgY2l1ZGFkLCBDYXBpdGFsLCBzdXJmYWNlQ2xhc3MpKQ0KDQp0cmFpbl9kdW1teTIgPC0gdHJhaW5fZHVtbXlbLCBuYW1lcyh0cmFpbl9kdW1teSkgJWluJSBuYW1lcyh0ZXN0X2R1bW15KV0NCnRyYWluX2R1bW15MiRwcmljZSA8LSB0cmFpbiRwcmljZQ0KDQp0ZXN0X2R1bW15MiA8LSAgdGVzdF9kdW1teVssIG5hbWVzKHRlc3RfZHVtbXkpICVpbiUgbmFtZXModHJhaW5fZHVtbXkyKV0NCnRyYWluX2R1bW15Mg0KYGBgDQoNCiMjIEFDUA0KDQpgYGB7cn0NCmxpYnJhcnkoRmFjdG9NaW5lUikNCmxpYnJhcnkoZmFjdG9leHRyYSkNCg0KYWNwIDwtIFBDQShYID0gdHJhaW5fZHVtbXkyICU+JSBzZWxlY3QoLXByaWNlKSwNCiAgICAgICAgICAgc2NhbGUudW5pdCA9IFRSVUUsIG5jcCA9IDEwLCBncmFwaCA9IEZBTFNFKQ0Kc3VtbWFyeShhY3ApDQpgYGANCg0K